import os
import subprocess
import time
import filecmp
from optparse import OptionParser, OptionGroup
from glob import glob

testsdir = ".\\tests\\tests\\"
answersdir = ".\\tests\\answers\\"
compiler_options = ''

class HangoverException:
    def __init__(self, testname):
        self.test = testname
    def __str__(self):
        return "'{0}' taked more than '{1}' seconds, and have been terminated".format(self.test, options.maxtime)
class NonZeroExitCodeException:
    def __init__(self, app):
        self.app = app
    def __str__(self):
        return "'{0}' exited with non zero code".format(self.app)

def ask(question):
    while 1:
        res = raw_input("{0}[y/n] ".format(question)).lower();
        if res == "y":
            return True
        elif res == "n":
            return False

#cmd options
usage = """usage: %prog [options] [arg1] [arg2] ..."""
parser = OptionParser(usage=usage, )
parser.set_defaults(fasmdir=".\\fasm\\", ccompiler="bin\\release\\ccompiler.exe", maxtime=5, quiet=False)
obligate = OptionGroup(parser, "Obligate options", "One of this options must be specified, if there is more than one, the last one will be used");
parser.add_option_group(obligate);
obligate.add_option('-t', '--tokenizer-only', action="store_const", const="tokenizer", dest="target2test", help="test the tokenizer")
obligate.add_option('-e', '--expressions-parser-only', action="store_const", const="expressions-parser", dest="target2test", help="test the expressions parser")
obligate.add_option('-E', '--expressions-parser2-only', action="store_const", const="expressions-parser2", dest="target2test", help="test the expressions parser, second ver")
obligate.add_option('-s', '--parser-with-symtable-only', action="store_const", const="parser-with-symtable", dest="target2test", help="test the parser, with symbol table")
obligate.add_option('-p', '--parser-only', action="store_const", const="parser", dest="target2test", help="test the full parser");
obligate.add_option('-g', '--generator-only', action="store_const", const="generator", dest="target2test", help="test the generator");
customBehaviour = OptionGroup(parser, "Customizing tester behaviour", "These options will customise enviroment and behaviour, they are not obligate")
parser.add_option_group(customBehaviour);
customBehaviour.add_option('--testsdir', action="store", dest="testsdir", type='string', help="directory for current tests(default is \\tests\\tests\\[optname without only]");
customBehaviour.add_option('--answersdir', action="store", dest="answersdir", type='string', help="directory for current answers(default is \\tests\\answers\\[optname without only]");
customBehaviour.add_option('--fasmdir', action="store", dest="fasmdir", type='string', help="directory with fasm.exe(default is \\fasm");
customBehaviour.add_option('--namemask', action="store", dest="namemask", type='string', help="mask for test names(for generator is *.c by default and *.tst for rest)");
customBehaviour.add_option('--ccompiler', action="store", dest="ccompiler", type="string", help="path to ccompiler(default is \\bin\\release\\ccompiler.exe)")
customBehaviour.add_option('--maxtime', action="store", dest="maxtime", type="int", help="max time for each test completion")
customBehaviour.add_option("-q", "--quiet", action="store_true", dest="quiet", help = "supress most of qestions");


(options, args) = parser.parse_args()

if options.target2test is None:
    parser.print_help()
    exit()


def runtimeCheck(a, testname, executableName=options.ccompiler):
    t = time.clock()
    while a.poll() is None:
        if (time.clock() - t) > options.maxtime:
            raise HangoverException(testname)
    if a.poll() != 0: raise NonZeroExitCodeException(executableName)

def commonAnsProcessing(answerfile, outfile, testname):
    if not os.path.exists(answerfile):
        print("\tfile '{0}' with answer wasn't found in '{1}', the output is:".format(os.path.basename(answerfile), answersdir))
        for line in open(outfile, 'r'):
            print (line.rstrip('\n'))
        if options.quiet: return 0
        if ask("Do you want to create a corresponding answer file from this output?"):
            os.rename(outfile, answerfile)
        return 0
    else:
        if filecmp.cmp(answerfile, outfile):                                                 
            print("{0} - passed".format(testname))
        else: 
            print("{0} - failed".format(testname))
            if options.quiet: return 1
            if ask("Do you want to see program output?"):
                for line in open(outfile, "r"): print(line.rstrip())
            if ask("Do you want to see answer?"):
                for line in open(answerfile, "r"): print(line.rstrip())
            if ask("Do you want to replace answer with program output?"):
                os.remove(answerfile)
                os.rename(outfile, answerfile)    
                                                                                                                                  
def simpleTester(testname, option):
    testname = os.path.basename(testname)
    filename = os.path.splitext(testname)[0]
    outfile = os.path.join(answersdir, filename + ".out")
    answerfile = os.path.join(answersdir, filename + ".ans")
    with open(outfile, 'w') as out: 
        a = subprocess.Popen(
            args = [os.path.basename(options.ccompiler), option, os.path.join(testsdir, testname)], 
            executable = options.ccompiler, 
            stdout = out,
            stderr = out)
    runtimeCheck(a, testname)
    #errors =  a.stderr.read()
    #if len(errors) : print("{0} - failed".format(testname))     
    return commonAnsProcessing(answerfile, outfile, testname)
    
def fullCompileCycleTester(testname, option):
    testname = os.path.basename(testname)
    filename = os.path.splitext(testname)[0]
    outfile = os.path.join(options.fasmdir, filename + ".asm")
    answerfile = os.path.join(answersdir, filename + ".ans")
    a = subprocess.Popen(
        args = [os.path.basename(options.ccompiler), option, os.path.join(testsdir, testname)], 
        executable = options.ccompiler, 
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE)
    runtimeCheck(a, testname)
    errors = a.stderr.readlines()
    if len(errors):
        for line in errors: print(line)
        print("{0} - failed".format(testname))
        return 0
    for asm in glob(os.path.join(testsdir, filename + ".asm")):
        to =  os.path.join(options.fasmdir, os.path.basename(asm))
        if os.path.exists(to): os.remove(to)
        os.rename(asm, to) 
    fasmExe = os.path.join(options.fasmdir, 'fasm.exe')
    b = subprocess.Popen([os.path.basename(fasmExe), outfile], executable=fasmExe, stdout=subprocess.PIPE)
    if b.wait() != 0: NonZeroExitCodeException(fasmExe)
    executable = os.path.join(fasmdir, filename + '.exe')
    outfile2 = os.path.join(answersdir, filename + ".out")
    compiled = subprocess.Popen(args=[os.path.basename(executable)], executable=executable, stdout=open(outfile2, "w"))
    runtimeCheck(compiled, testname, executable)
    return commonAnsProcessing(answerfile, outfile2, testname)

get_dir = lambda **dir: (getattr(options, i[0]) or i[1] + options.target2test.replace('-', ' ') for i in dir.items())
(answersdir, testsdir) = get_dir(testsdir=testsdir, answersdir=answersdir)

fasmdir = options.fasmdir
namemask = options.namemask if not options.namemask is None else "*.c" if options.target2test == "generator" else "*.tst"
if len(args) != 0:
    tests = [os.path.join(testsdir, arg) for arg in args]
else:
    tests = glob(os.path.join(testsdir, namemask))
tests.sort()

for test in tests:
    testfunc = simpleTester if options.target2test != "generator" else fullCompileCycleTester # temp
    try:
        testfunc(test, "--{0}-only".format(options.target2test))
    except (HangoverException, NonZeroExitCodeException) , e:
        print("{0} - {1}".format(os.path.basename(test), e))           